package cau.se.project.view;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import cau.se.project.data.method.GroupControl;
import cau.se.project.data.method.RowControl;
import cau.se.project.method.CLSXControl;
import cau.se.project.view.method.DSMChangeListener;

public class CLSXTreeView extends Composite {
	
	private Tree m_tree;
	private CLSXControl clsx = null;
	private DSMChangeListener dsmChangeListener = null;
	private DragSource ds;
	private DropTarget dt;
	
	private Menu rightMenu = null;
	private MenuItem item_rename, item_sort, item_group, item_ungroup, item_delete;
	
	private TreeItem selectedItem = null;

	public CLSXTreeView(Composite composite, int style) {
		super(composite, SWT.NONE);
		setLayout(new FillLayout(SWT.HORIZONTAL));

		m_tree = new Tree(this, SWT.BORDER | SWT.MULTI);
		m_tree.setDragDetect(true);
		
		// Mouse click listener
		m_tree.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseUp(MouseEvent e) {
				super.mouseDown(e);

				// Right click
				if(e.button == 3) {
					TreeItem i = m_tree.getItem(new Point(e.x, e.y));
					if(i == null) return;
					
					showMouseRightMenu();
					System.out.println("right click" + m_tree.getItem(new Point(e.x, e.y)));
				}
			}
		});
		
		// Expand listener
		m_tree.addListener(SWT.Expand, new Listener() {
			
			@Override
			public void handleEvent(Event arg0) {
				TreeItem item = (TreeItem) arg0.item;
				System.out.println("expand");
				expandOrCollapsed(item);
			}
		});
		
		// Collapse listener
		m_tree.addListener(SWT.Collapse, new Listener() {
			
			@Override
			public void handleEvent(Event arg0) {
				TreeItem item = (TreeItem) arg0.item;
				System.out.println("collapse");
				expandOrCollapsed(item);
			}
		});
		
		// Select listener
		m_tree.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event e) {
				TreeItem[] selection = m_tree.getSelection();
				if(selection.length == 0)
					return;

				// rcTo find
				TreeItem it = (TreeItem) e.item;
				RowControl rowSelected = mapItem(it);
				
				emitSelectionChanged(rowSelected);
				System.out.println(selection[0].getText() + " selected");
			}
		});
		
		// Drag listener
		ds = new DragSource(m_tree, DND.DROP_MOVE);
		ds.setTransfer(new Transfer[]{TextTransfer.getInstance()});
		ds.addDragListener(new DragSourceAdapter() {
			@Override
			public void dragStart(DragSourceEvent e) {
				selectedItem = m_tree.getSelection()[0];
				System.out.println(String.format("selected item : %s", selectedItem.getText()));
				super.dragStart(e);
			}
			
			@Override
			public void dragSetData(DragSourceEvent e) {
				e.data = selectedItem.getText();
				System.out.println("set drag data " + e.data);
				super.dragSetData(e);
			}
			
			@Override
			public void dragFinished(DragSourceEvent e) {
				if(e.detail == DND.DROP_MOVE) {
					System.out.println("drag finished");
					selectedItem = null;
				}
				super.dragFinished(e);
			}
		});
		
		// Drop listener
		dt = new DropTarget(m_tree, DND.DROP_MOVE);
		dt.setTransfer(new Transfer[]{TextTransfer.getInstance()});
		dt.addDropListener(new DropTargetAdapter() {
			@Override
			public void drop(DropTargetEvent e) {
				if(selectedItem == null)
					return;
				
				if(e.item == null)
					return;
				
				System.out.println("drop item " + e.data);

				// rcFrom find
				Stack<TreeItem> stack = new Stack<>();
				TreeItem it = selectedItem;
				while(it.getParentItem() != null) {
					stack.push(it);
					it = it.getParentItem();
				}
				
				RowControl rcFrom = (RowControl) clsx.getRoot();
				while(stack.isEmpty() == false) {
					it = stack.pop();
					for(int i=0; i<rcFrom.getChildCount(); i++) {
						if(((RowControl)rcFrom.getChildAt(i)).getName().equals(it.getText())) {
							rcFrom = (RowControl) rcFrom.getChildAt(i);
							continue;
						}
					}
				}
				System.out.println("rcFrom: " + rcFrom.getName());
				
				// rcTo find
				it = (TreeItem) e.item;
				RowControl rcTo = mapItem(it);
				System.out.println("rcto: " + rcTo.getName());
				
				try {
					clsx.moveRowTo(rcFrom, rcTo, isUpperIndicated(e));
					emitCLSXChanged();
				} catch (Exception e1) {
					MessageBox msg = new MessageBox(getShell());
					msg.setMessage("아이템 옮기는 중 에러\n" + e1.getMessage());
					msg.open();
					e1.printStackTrace();
				}
				
				super.drop(e);
			}
			
			@Override
			public void dragOver(DropTargetEvent e) {
				TreeItem item = (TreeItem) e.item;
				if(item != null) {
					System.out.println("drag over " + item.getText());
					e.detail = DND.DROP_MOVE;
					e.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;

					if(isUpperIndicated(e)) {
						e.feedback |= DND.FEEDBACK_INSERT_BEFORE;
					}
					else {
						e.feedback |= DND.FEEDBACK_INSERT_AFTER;
					}
					
				}
				else {
					// == null
					e.detail = DND.DROP_NONE;
				}
				
				super.dragOver(e);
			}
			
			private boolean isUpperIndicated(DropTargetEvent e)
			{
				TreeItem item = (TreeItem) e.item;
				Point p = m_tree.getDisplay().map(null, m_tree, e.x, e.y);
				Rectangle rect = item.getBounds();
				if(p.y < rect.y + rect.height/2) {
					return true;
				}
				else {
					return false;
				}
			}
		});
	}
	
	public void refresh(CLSXControl clsx)
	{		
		this.clsx = clsx;

		refresh();
		expandAll();
	}
	
	private void refresh()
	{
		m_tree.removeAll();
		
		if(clsx == null)
			return;
		
		System.out.println("make treeview");
		TreeItem root = new TreeItem(m_tree, 0);
		root.setText(((GroupControl)clsx.getRoot()).getName());
		makeTree((GroupControl) clsx.getRoot(), root);
	}
	
	private void makeTree(GroupControl group_dsm, TreeItem group_tree)
	{
		for(int i=0; i<group_dsm.getChildCount(); i++) {
			RowControl rc = (RowControl) group_dsm.getChildAt(i);
			TreeItem item = new TreeItem(group_tree, -1);
			item.setText(rc.getName());
			
			if(rc.isLeaf() == false) {
				makeTree((GroupControl) rc, item);
			}
		}
	}
	
	private void expandOrCollapsed(TreeItem itemChanged)
	{
		List<RowControl> list = new ArrayList<>();
		inputList(m_tree.getItem(0), list, itemChanged);
		dsmChangeListener.onExpandChanged(list);
	}
	
	private void inputList(TreeItem item, List<RowControl> list, TreeItem itemChanged)
	{
		// item == itemchanged의 경우는 getexpanded가 반대로 결과가 출력됨
		if(item == itemChanged ? !item.getExpanded() : item.getExpanded()) {
			for(int i=0; i<item.getItemCount(); i++) {
				inputList(item.getItem(i), list, itemChanged);
			}
		}
		else {
			RowControl rc = mapItem(item);
			if(!rc.isLeaf()) {
				System.out.println("num of " + rc.getName() + "'s dependency: " + rc.getDepenencyList().size());
			}
			list.add(mapItem(item));
		}
	}
	
	public void expandAll()
	{
		System.out.println("Expand All");
		expand(m_tree.getItem(0));
		expandOrCollapsed(null);
	}
	
	private void expand(TreeItem item)
	{
		for(TreeItem i : item.getItems()) {
			if(i.getItemCount() != 0)
				expand(i);
		}
		item.setExpanded(true);
	}
	
	public void collapseAll()
	{
		System.out.println("Collapse All");
		collapse(m_tree.getItem(0));
		expandOrCollapsed(null);
	}
	
	private void collapse(TreeItem item)
	{
		item.setExpanded(false);
		for(TreeItem i : item.getItems()) {
			if(i.getItemCount() != 0)
				collapse(i);
		}
	}
	
	public void select(RowControl row)
	{
		if(getSelectedItems().length > 1)
			return;
		
		m_tree.deselectAll();
		
		if(row == null) return;
		
		select((GroupControl) clsx.getRoot(), m_tree.getItem(0), row);
	}
	
	public void select(RowControl[] rows)
	{
		m_tree.deselectAll();
		
		if(rows == null) return;
		
		for(int i=0; i<rows.length; i++)
			select((GroupControl) clsx.getRoot(), m_tree.getItem(0), rows[i]);
	}
	
	private boolean select(GroupControl group, TreeItem groupNode, RowControl row)
	{
		if(group == row)
			m_tree.select(groupNode);
		
		for(int i=0; i<group.getChildCount(); i++) {
			RowControl child = (RowControl) group.getChildAt(i);
			if(child == row) {
				m_tree.select(groupNode.getItem(i));
				return true;
			}
			else if(child.isLeaf() == false) {
				boolean ret = select((GroupControl) child, groupNode.getItem(i), row);
				if(ret)
					return ret;
			}
		}
		
		return false;
	}
	
	public RowControl getSelectedItem() {
		TreeItem[] items = m_tree.getSelection();
		if(items == null || items.length == 0)
			return null;

		return mapItem(items[0]);
	}
	
	public RowControl[] getSelectedItems()
	{
		TreeItem[] items = m_tree.getSelection();
		if(items == null || items.length == 0)
			return new RowControl[0];
		
		RowControl[] ret = new RowControl[items.length];
		for(int i = 0; i<items.length; i++) {
			ret[i] = mapItem(items[i]);
		}

		return ret;
	}
	
	private RowControl mapItem(TreeItem item)
	{
		TreeItem it = item;
		Stack<TreeItem> stack = new Stack<>();
		while(it.getParentItem() != null) {
			stack.push(it);
			it = it.getParentItem();
		}
		
		RowControl rowSelected = (RowControl) clsx.getRoot();
		w:
		while(stack.isEmpty() == false) {
			it = stack.pop();
			for(int i=0; i<rowSelected.getChildCount(); i++) {
				if(((RowControl)rowSelected.getChildAt(i)).getName().equals(it.getText())) {
					rowSelected = (RowControl) rowSelected.getChildAt(i);
					continue w;
				}
			}
		}
		
		return rowSelected;
	}
	
	private void showMouseRightMenu()
	{
		if(rightMenu == null) {
			// Popup menu
			rightMenu = new Menu(getShell(), SWT.POP_UP);
			
			// sort menu
			item_sort = new MenuItem(rightMenu, SWT.PUSH);
			item_sort.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent arg0) {
					if(dsmChangeListener != null)
						dsmChangeListener.onRequestSort();
					super.widgetSelected(arg0);
				}
			});
			item_sort.setText("이름으로 정렬");
			
			// Delete menu
			item_delete = new MenuItem(rightMenu, SWT.PUSH);
			item_delete.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent arg0) {
					if(dsmChangeListener != null)
						dsmChangeListener.onRequestDelete();
					super.widgetSelected(arg0);
				}
			});
			item_delete.setText("삭제");
			
			// Group menu
			item_group = new MenuItem(rightMenu, SWT.PUSH);
			item_group.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent arg0) {
					if(dsmChangeListener != null)
						dsmChangeListener.onRequestGroup();
					super.widgetSelected(arg0);
				}
			});
			item_group.setText("그룹으로 합치기");
			
			// Ungroup menu
			item_ungroup = new MenuItem(rightMenu, SWT.PUSH);
			item_ungroup.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent arg0) {
					if(dsmChangeListener != null)
						dsmChangeListener.onRequestUngroup();
					super.widgetSelected(arg0);
				}
			});
			item_ungroup.setText("그룹 해제");
			
			// Rename menu
			item_rename = new MenuItem(rightMenu, SWT.PUSH);
			item_rename.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent arg0) {
					if(dsmChangeListener != null)
						dsmChangeListener.onRequestRename();
					super.widgetSelected(arg0);
				}
			});
			item_rename.setText("이름 바꾸기");
		}
		
		RowControl rc = getSelectedItem();
		if(rc == null) return;
		
		item_delete.setEnabled(rc.isLeaf());
		item_ungroup.setEnabled(!rc.isLeaf());
		item_sort.setEnabled(!rc.isLeaf());
		
		rightMenu.setVisible(true);
	}
	
	private void emitDSMChanged()
	{
		if(dsmChangeListener != null)
			dsmChangeListener.onDSMChanged();
	}
	
	private void emitCLSXChanged()
	{
		if(dsmChangeListener != null)
			dsmChangeListener.onCLSXChanged();
	}
	
	private void emitSelectionChanged(RowControl row)
	{
		if(dsmChangeListener != null)
			dsmChangeListener.onSelectionChanged(row);
	}

	public void setOnDSMChangeListener(DSMChangeListener l)
	{
		this.dsmChangeListener = l;
	}
	
	@Override
	public void addKeyListener(KeyListener arg0) {
		m_tree.addKeyListener(arg0);
		super.addKeyListener(arg0);
	}
}
